#pragma once
#include <iostream>
#include <unistd.h>
//在进行网络程序编写时这四个头文件是固定的
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#include <cstring>
#include <string>
#include "Log.hpp"

static const int gsockfd = -1;
static const uint16_t glocalport = 8888;//端口号默认初始为8888
enum{
    SOCKET_ERROR = 1,
    BIND_ERROR
};
class UdpServer
{
public:
    UdpServer(uint16_t localport=glocalport /*,std::string localip="123.0.0.1"*/)
    :_sockfd(gsockfd)
    ,_localport(localport)
    //,_localip(localip)
    ,_isRunning(false)//默认是false，因为该程序并没有启动
    {}
    //服务端对象一般禁止拷贝
    UdpServer(const UdpServer&) = delete;
    const UdpServer& operator=(const UdpServer&) = delete;
    void InitServer()
    {
        //1. 创建socket文件
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);//这里的AF_INET表示创建的文件是进行网络通信的网络文件
        //判断socket文件是否创建成功
        if(_sockfd < 0)
        {
            LOG(FATAL, "socket error\n");//这是一个致命错误
            exit(SOCKET_ERROR);//ocket文件创建失败也就没有必要再继续下去了
        }
        LOG(DEBUG, "socket creat success _sockfd: %d\n", _sockfd);//打印出来的文件描述符是3

        //2. 将本地的ip地址、该程序的端口号和创建好的socket文件关联起来
        struct sockaddr_in local; 
        //先将local变量清0
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET; //这里的AF_INET表示local中的信息是网络的信息，
        // 如果这里的和上面的两个字段不一样，在关联的时候会报错
        //在通过网络通信时需要将自己的端口号和ip地址也要通过网络发给远端，因为远端需要给自己会信息，也就需要知道自己的IP地址和端口号
        //所以需要将主机字节序转换成网络字节序
        local.sin_port = htons(_localport);
        //需要4字节风格的IP 2.需要网络序列的IP
        //local.sin_addr.s_addr = inet_addr(_localip.c_str());
        local.sin_addr.s_addr = INADDR_ANY;
        //bind
        int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));
        if(n < 0)
        {
            //绑定失败直接退出程序没必要继续执行
            LOG(FATAL, "bind error\n");
            exit(BIND_ERROR);
        }
        LOG(DEBUG, "bind creat success\n");
    }
    //服务器上跑的程序是一直运行的，
    void Start()
    {
        //启动程序
        _isRunning = true;
        char in_buffer[1024];
        while(_isRunning)
        {
            //每次收到的信息可能来自不同的远端主机，所以peer定义在while循环内部
            struct sockaddr_in peer;//需要知道那个客户端发的消息，谁发的消息
            socklen_t len = sizeof(peer);
            //udp有些特殊不能使用read和write来读写socket文件，只能使用recvfrom
            //第5个参数是通用接口所以要对&peer进行强转
            ssize_t n = recvfrom(_sockfd, in_buffer, sizeof(in_buffer)-1, 0, (struct sockaddr*)&peer, &len);
            //将服务器收到的消息在原来消息的基础上添加内容再发回去
            if(n > 0)
            {
                in_buffer[n] = 0;
                std::string echo_string = "[sever echo]#";
                echo_string += in_buffer;
                sendto(_sockfd, echo_string.c_str(), sizeof(echo_string), 0, (struct sockaddr*)&peer, len);
            }
        }
    }
    ~UdpServer()
    {
        if(_sockfd > gsockfd) close(_sockfd);
    }
private:
    int _sockfd;
    uint16_t _localport;//端口号占2字节
    //std::string _localip;
    bool _isRunning;
};